/*游戏房间管理模块---游戏房间类实现*/
#ifndef __M_ROOM_H__
#define __M_ROOM_H__
#include "logger.hpp"
#include "util.hpp"

#include "db.hpp"
#include "online.hpp"
#include <iostream>

#define BOARD_ROW 15
#define BOARD_COL 15
#define CHESS_WHITE 1
#define CHESS_BLACK 2
typedef enum{GAME_START,GAME_OVER}room_statu;

class room{
private:
    uint64_t _room_id;//房间号
    room_statu _statu;//房间状态
    int _player_count;//在线人数
    uint64_t _white_id;//白棋id
    uint64_t _black_id;//黑棋id
    user_table* _tb_user;//用户表
    online_manager* _online_user;//在线用户
    std::vector<std::vector<int>> _board;//棋盘15X15
private:

    /// @brief 判断四个方向是否连续出现五枚棋子
    /// @param row 下棋位置行 
    /// @param col 下棋位置列
    /// @param row_off 行偏移量
    /// @param col_off 列偏移量
    /// @param color 棋子颜色
    /// @return 是否五子连珠
    bool five(int row,int col,int row_off,int col_off,int color)
    {
        int count=1;
        int search_row=row+row_off;
        int search_col=col+col_off;
        while(search_row >=0 && search_row<BOARD_ROW &&
                search_col>=0&&search_col<BOARD_COL &&
                _board[search_row][search_col]==color)
        {
            //同色棋子数量++
            count++;
            //检索位置继续向后偏移
            search_row+=row_off;
            search_col+=col_off;
        }

        //反向计算同色棋子数
        search_row=row-row_off;
        search_col=col-col_off;
        while(search_row >=0 && search_row<BOARD_ROW &&
                search_col>=0&&search_col<BOARD_COL &&
                _board[search_row][search_col]==color)
        {
            //同色棋子数量++
            count++;
            //检索位置继续向后偏移
            search_row-=row_off;
            search_col-=col_off;
        }
        return (count>=5);
    }

    /// @brief 判断获胜者
    /// @param row 
    /// @param col 
    /// @param color 
    /// @return 获胜者id
    uint64_t check_win(int row,int col,int color)
    {
        if(five(row,col,0,1,color) || //横向：行不变，列加减
        five(row,col,1,0,color)||//纵向：列不变：行加减
        five(row,col,-1,1,color)||//正斜线：右上:行减列加；左下：行加列减
        five(row,col,-1,-1,color))//反斜线：左上:行列减；右下：行列加
        {
            //任意一个方向满足就获胜
            return color==CHESS_WHITE?_white_id:_black_id;
        }
        return 0;
    }    
public:
    room(uint64_t room_id,user_table* tb_user,online_manager* online_user)
        :_room_id(room_id),_statu(GAME_START),_player_count(0),
        _tb_user(tb_user),_online_user(online_user) ,
        _board(BOARD_ROW,std::vector<int>(BOARD_COL,0)) 
    {
        DLOG("%lu 房间创建成功!!!",_room_id);
    }  

    ~room()
    {
        DLOG("%lu 房间销毁成功!!!",_room_id);
    }

    uint64_t id(){return _room_id;}//获取房间id
    room_statu statu(){return _statu;}//获取房间状态
    int player_count(){return _player_count;}//获取当前房间人数
    void add_white_user(uint64_t uid){_white_id=uid;_player_count++;}//添加白棋手
    void add_black_user(uint64_t uid){_black_id=uid;_player_count++;}//添加黑棋手
    uint64_t get_white_user(){return _white_id;}//获取白棋手
    uint64_t get_black_user(){return _black_id;}//获取黑棋手

    /*处理下棋动作*/
    Json::Value handle_chess(Json::Value& req){
        Json::Value json_resp=req;
        //1.当前请求的房间号是否与当前房间号匹配
            // handle_request已经进行判断
        //2.判断房间中两个玩家是否都在线，任意一个人掉线，对方胜利
        int chess_row=req["row"].asInt();
        int chess_col=req["col"].asInt();
        uint64_t cur_uid=req["uid"].asInt64();//当前棋手id
        if(_online_user->is_in_game_room(_white_id)==false)
        {
            
            json_resp["result"]=true;
            json_resp["reason"]="对手跑路，不战而胜!";
            json_resp["winner"]=(Json::UInt64)_black_id;//白棋跑路，黑棋赢
            return json_resp;
        }

        if(_online_user->is_in_game_room(_black_id)==false)
        {
           
            json_resp["result"]=true;
            json_resp["reason"]="对手跑路，不战而胜!";
            json_resp["winner"]=(Json::UInt64)_white_id;//黑棋跑路，白棋赢
            
            return json_resp;

        }
        //3.获取走棋位置,判断当前走棋是否合理（位置是否被占用）
        if(_board[chess_row][chess_col]!=0)
        {
            
            json_resp["result"]=false;
            json_resp["reason"]="当前位置已被占用";
            return json_resp;
        }
        int cur_color=cur_uid==_white_id?CHESS_WHITE:CHESS_BLACK;
        _board[chess_row][chess_col]=cur_color;
        //4.判断是否有玩家胜利
        uint64_t winner_id = check_win(chess_row, chess_col, cur_color);
        if (winner_id != 0) {
            json_resp["reason"] = "日月如合璧，五星如连珠";  
        }
        
        json_resp["result"] = true;
        json_resp["winner"] = (Json::UInt64)winner_id;
        return json_resp;
    }

    /*处理聊天动作*/
    Json::Value handle_chat(Json::Value& req)
    {
        Json::Value json_resp=req;
        //检测消息中是否包含敏感词
        std::string msg=req["message"].asString();
        size_t pos=msg.find("废物");
        if(pos!=std::string::npos)
        {
            json_resp["result"]=false;
            json_resp["reason"]="消息包含敏感词汇，不能发送！";
            return json_resp;
        }

        //广播消息---返回消息
        json_resp["result"]=true;
        return json_resp;        

    }

    /// @brief 处理玩家退出房间动作
    /// @param uid 
    void handle_exit(uint64_t uid)
    {
        //如果是下棋中退出，则对方获胜
        //如果是下棋结束退出，正常退出
        Json::Value json_resp;
        if(_statu==GAME_START)
        {
            uint64_t winner_id = (Json::UInt64)(uid == _white_id ? _black_id : _white_id);
                json_resp["optype"] = "put_chess";
                json_resp["result"] = true;
                json_resp["reason"] = "对方跑路，不战而胜！";
                json_resp["room_id"] = (Json::UInt64)_room_id;
                json_resp["uid"] = (Json::UInt64)uid;
                json_resp["row"] = -1;
                json_resp["col"] = -1;
                json_resp["winner"] = (Json::UInt64)winner_id;
                uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;
                _tb_user->win(winner_id);
                _tb_user->lose(loser_id);
                _statu = GAME_OVER;
                broadcast(json_resp);
        }
        _player_count--;
        return;

    }

    /// @brief 总的请求处理函数，根据不同请求调用不同的处理函数，得到响应进行广播
    /// @param req 
    void handle_request(Json::Value &req)
    {
        //1.校验房间号是否匹配
        Json::Value json_resp;
        uint64_t room_id=req["room_id"].asUInt64();
        if(room_id != _room_id)
        {
            json_resp["optype"]=req["optype"].asString();
            json_resp["result"]=false;
            json_resp["reason"]="房间号不匹配";
            return  broadcast(json_resp);
        }
        //2.根据不同的请求类型调用不同的处理函数
        if(req["optype"].asString()=="put_chess")
        {
            json_resp=handle_chess(req);
            if(json_resp["winner"].asUInt64()!=0)
            {
                uint64_t winner_id=json_resp["winner"].asUInt64();
                //更新数据库信息
                uint64_t loser_id=winner_id==_white_id?_black_id:_white_id;
                _tb_user->win(winner_id);
                _tb_user->lose(loser_id);
                _statu=GAME_OVER;
            }   
        }
        else if(req["optype"].asString()=="chat")
        {
            json_resp=handle_chat(req);
        }
        else
        {
            json_resp["optype"]=req["optype"].asString();
            json_resp["result"]=false;
            json_resp["reason"]="未知请求类型";
        }
        std::string body;
        json_util::serialize(json_resp, body);
        DLOG("房间-广播动作: %s", body.c_str());
        return  broadcast(json_resp);

    }

    /*将指定信息广播给房间中所有玩家*/
    void broadcast(Json::Value &rsp)
    {
        //1.对响应信息进行序列化，将json::value中的数据序列化成为json格式字符串
        std::string body;
        json_util::serialize(rsp,body);

        //2.获取房间中所有用户的通信连接
        wsserver_t::connection_ptr wconn=_online_user->get_conn_from_room(_white_id);
        wsserver_t::connection_ptr bconn=_online_user->get_conn_from_room(_black_id);
        //3.发送响应信息
        if(wconn.get() !=nullptr)
        {
            wconn->send(body);
        }
        else
        {
            DLOG("房间-白棋玩家连接获取失败");
        }
         if(bconn.get() !=nullptr)
        {
            bconn->send(body);
        }
        else
        {
            DLOG("房间-黑棋玩家连接获取失败");
        }
        return;
    }
};


using room_ptr=std::shared_ptr<room>;


class room_manager{
private:    
    uint64_t _next_rid;//房间id
    std::mutex _mutex;
    user_table* _tb_user;
    online_manager* _online_user;
    /*房间信息管理：房间id与房间信息的映射关系*/
    std::unordered_map<uint64_t,room_ptr> _rooms;
    /*房间id与用户id的关联关系：通过用户id找到房间id，再去找房间信息*/
    std::unordered_map<uint64_t,uint64_t> _users;
public:
    /*初始化房间id计数器*/
    room_manager(user_table* ut,online_manager* om)
        :_next_rid(1),_tb_user(ut),_online_user(om)
    {
        DLOG("房间管理模块初始化完毕！");
    }
    ~room_manager(){
        DLOG("房间管理模块即将销毁！");
    }

    /*为两个用户创建房间，并返回房间的智能指针*/
    room_ptr create_room(uint64_t uid1,uint64_t uid2)
    {
        //两个用户在游戏大厅中进行对战匹配，匹配成功后创建房间
        //1.校验两个用户是否都在游戏大厅，全在创建
        if(_online_user->is_in_game_hall(uid1)==false)
        {
            DLOG("用户：%lu 不在大厅中，创建房间失败",uid1);
            return room_ptr();
        }
        if(_online_user->is_in_game_hall(uid2)==false)
        {
            DLOG("用户：%lu 不在大厅中，创建房间失败",uid2);
            return room_ptr();
        }
        //2.创建房间，将用户信息添加到房间中
        std::unique_lock<std::mutex> lock(_mutex);//加锁保护
        room_ptr rp(new room(_next_rid,_tb_user,_online_user));
        rp->add_white_user(uid1);//添加白子用户
        rp->add_black_user(uid2);//添加黑子用户
        //3.将房间信息管理起来:映射关系
        _rooms.insert(std::make_pair(_next_rid,rp));
        _users.insert(std::make_pair(uid1,_next_rid));
        _users.insert(std::make_pair(uid2,_next_rid));
        DLOG("房间%d 创建成功",_next_rid);
        _next_rid++;
        //4.返回房间信息 
        return rp;
    }
    /*通过房间id获取房间的智能指针*/
    room_ptr get_room_by_rid(uint64_t rid)
    {
        std::unique_lock<std::mutex> lock(_mutex);//加锁保护
        auto it=_rooms.find(rid);
        if(it==_rooms.end()){
            return room_ptr();
        }
        return it->second;
    }
    /*通过用户id获取房间的智能指针*/
    room_ptr get_room_by_uid(uint64_t uid)
    {
        std::unique_lock<std::mutex> lock(_mutex);//加锁保护
        //1.通过用户id获取房间id
        auto it=_users.find(uid);
        if(it==_users.end()){
            return room_ptr();
        }
        uint64_t rid=it->second;
        //2.通过房间id获取房间信息
        //不能调用上面的接口，死锁问题
        auto rit=_rooms.find(rid);
        if(rit==_rooms.end()){
            return room_ptr();
        }
        return rit->second;
    }

    /*根据房间id销毁房间*/
    void remove_room(uint64_t rid)
    {
        //房间信息通过shared_ptr在_rooms中进行管理，只需要将shared_ptr从rooms移除
        //shared_ptr计数器为0的时候，外界没有对房间信息进行操作保存就会销毁
        //1.通过房间id获取房间信息
        room_ptr rp=get_room_by_rid(rid);
        if(rp.get()==nullptr){return;}
        //2.通过房间id获取两个用户信息
        uint64_t uid1=rp->get_white_user();
        uint64_t uid2=rp->get_black_user();
        //3.移除房间管理中的用户信息
        std::unique_lock<std::mutex> lock(_mutex);//加锁保护
        _users.erase(uid1);
        _users.erase(uid2);
        //4.通过rid移除房间管理信息shared_ptr
        _rooms.erase(rid);
    }

    /*删除房间中指定用户，如果房间没有用户，销毁，用户断开连接的时候被调用*/
    void remove_room_user(uint64_t uid)
    {
        room_ptr rp=get_room_by_uid(uid);
        if(rp.get()==nullptr){return;}
        rp->handle_exit(uid);//退出房间
        if(rp->player_count()==0){//销毁
            remove_room(rp->id());
        }
        return;
    }

};

#endif


